りおんクロニクル


SQLite × マルチスレッド安全設計|ロックしない・壊れない・高速な実務パターン【2026年版】

Home【2026年版】C# / .NET入門と実践ガイド|基礎・業務アプリ開発・SQLite連携まで体系的に解説

SQLiteは軽量で高速ですが、マルチスレッド環境ではロックが発生しやすいという特徴があります。 特に C#(Microsoft.Data.Sqlite)で業務アプリを作る場合、 database is locked を防ぐ設計が必須です。

この記事でわかること
・SQLiteがマルチスレッドでロックしやすい理由
・スレッド安全な書き込み設計(直列化)
・WALモードの有効化
・書き込みキュー(Producer/Consumer)
・非同期処理との組み合わせ
・業務アプリ向けベストプラクティス

1. SQLiteは「同時書き込みが1つだけ」

SQLiteは単一ファイルDBのため、 同時に複数の書き込みを処理できません

database is locked の9割は「同時書き込み」が原因。

2. マルチスレッドで安全にする基本戦略

SQLiteを安全に使うには、次の3つが必須です。

  1. 書き込みを直列化する(排他制御)
  2. WALモードを有効化する
  3. トランザクションを短く保つ

3. 書き込みは lock で直列化する(最重要)

複数スレッドから書き込みが来る場合、 必ず1つずつ順番に処理する必要があります。

■ C#での排他制御

private static readonly object _writeLock = new();

public void UpdateUser(User user)
{
    lock (_writeLock)
    {
        using var con = new SqliteConnection(_cs);
        con.Open();

        var sql = "UPDATE Users SET Name=@name WHERE Id=@id";
        using var cmd = new SqliteCommand(sql, con);
        cmd.Parameters.AddWithValue("@name", user.Name);
        cmd.Parameters.AddWithValue("@id", user.Id);

        cmd.ExecuteNonQuery();
    }
}

ポイント

4. 書き込みキュー(Producer/Consumer)方式

業務アプリでは、書き込みを専用スレッドに集約するのが最も安全です。 UIスレッドや複数スレッドからの書き込み要求はキューに積み、 バックグラウンドスレッドが順番に処理します。

■ 書き込みキューの例

private readonly BlockingCollection<Action> _writeQueue = new();

public void StartWriteWorker()
{
    Task.Run(() =>
    {
        foreach (var action in _writeQueue.GetConsumingEnumerable())
        {
            action();
        }
    });
}

public void EnqueueWrite(Action action)
{
    _writeQueue.Add(action);
}

■ 使い方

EnqueueWrite(() =>
{
    using var con = new SqliteConnection(_cs);
    con.Open();

    var sql = "INSERT INTO Logs (Message) VALUES (@msg)";
    using var cmd = new SqliteCommand(sql, con);
    cmd.Parameters.AddWithValue("@msg", "ログ");
    cmd.ExecuteNonQuery();
});

これで書き込みは常に1つずつになり、locked がほぼ消えます。

5. WALモードを有効化する(読み書き並列に強くなる)

SQLiteはデフォルトではロックしやすいですが、 WAL(Write-Ahead Logging)モードにすると改善します。

■ WALモードの設定

using var con = new SqliteConnection(_cs);
con.Open();

using var cmd = new SqliteCommand("PRAGMA journal_mode=WAL;", con);
cmd.ExecuteNonQuery();

WALのメリット

6. トランザクションは短く・まとめて

トランザクション中は書き込みロックが保持されるため、 長時間のトランザクションは厳禁です。

■ 正しいパターン

using var tran = con.BeginTransaction();

try
{
    // INSERT/UPDATE をまとめて実行
    tran.Commit();
}
catch
{
    tran.Rollback();
}

ポイント

7. 非同期処理(async/await)との組み合わせ

UIスレッドでDBアクセスすると、 処理が詰まってロックが発生しやすくなります。

■ 非同期で書き込み

await cmd.ExecuteNonQueryAsync();

■ DataTable.Load は同期 → Task.Run で逃がす

await Task.Run(() => dt.Load(reader));

8. マルチスレッド安全設計の最終チェックリスト

まとめ:SQLiteは“設計次第で”マルチスレッドに強くなる

SQLiteは軽量で扱いやすい反面、 マルチスレッド設計を誤るとロック地獄になります。 この記事のパターンを採用すれば、 ロックしない・壊れない・高速なアプリを構築できます。

前のページ  次のページ